//
//  DPAsyncTestsRunner.m
//  UITestingKit
//
//  Created by Ofri Wolfus on 09/02/07.
//  Copyright 2007 Ofri Wolfus. All rights reserved.
//

#import "DPAsyncTestsRunner.h"
#import "DPTestsModule.h"
#import "DPTestStatistics.h"
#import "DPQualityAssurance-Private.h"


@interface DPAsyncTestsRunner (private)
- (void)moduleFinished:(NSNotification *)notif;
- (void)moduleFailed:(NSNotification *)notif;
- (void)logModuleStatistics:(DPTestsModule *)module;
@end


@implementation DPAsyncTestsRunner

- (id)initWithModules:(NSArray *)arr {
	if ((self = [super init])) {
		modules = [arr mutableCopy];
		finishedModules = [[NSMutableArray alloc] initWithCapacity:[modules count]];
		delegate = nil;
		catchExceptions = NO;
		stopOnFailure = YES;
		completeCount = 0U;
		modulesCount = [modules count];
		isVerbose = NO;
		[DPQualityAssurance setUpTesters];
	}
	
	return self;
}

- (void)dealloc {
	[DPQualityAssurance tearDownTesters];
	[delegate release]; delegate = nil;
	[modules release]; modules = nil;
	[finishedModules release]; finishedModules = nil;
	[super dealloc];
}

- (void)fire {
	if (!currentModule) {
		NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
		
		// The module is released in _finishModule:
		currentModule = [[modules lastObject] retain];
		
		// There are no modules left
		if (!currentModule) {
			if (completeCount == modulesCount) {
				/*NSEnumerator *enumerator = [finishedModules objectEnumerator];
				DPTestsModule *m;
				
				while ((m = [enumerator nextObject]))
					[self logModuleStatistics:m];*/
				
				[self invalidate];
				
				if (delegate && [delegate respondsToSelector:@selector(testsRunnerFinished:withStatus:)])
					[delegate testsRunnerFinished:self
									   withStatus:YES];
			}
			
			return;
		}
		
		[modules removeLastObject];
		[currentModule setStopsOnFailedTests:[self stopsOnFailedTests]];
		[currentModule setCatchExceptions:[self catchExceptions]];
		
		[nc addObserver:self
			   selector:@selector(moduleFinished:)
				   name:DPTestsModuleCompletedNotification
				 object:currentModule];
		[nc addObserver:self
			   selector:@selector(moduleFailed:)
				   name:DPTestsModuleFailedNotification
				 object:currentModule];
	}
	
	DPTestStatistics *statistics = [currentModule nextTest];
	
	if (statistics) {
		if ([self isVerbose] && [statistics status] != DPTestPending)
			//[self logStatistics:statistics];
			printf("%s\n", [[statistics log] UTF8String]);
	} else {
		// We're done with this module.
		// Start executing a new one.
		currentModule = nil;
		[self fire];
	}
	
	[self signal];
}

- (NSString *)logForModule:(DPTestsModule *)module {
	NSDictionary *cases = [module statistics];
	NSEnumerator *enumerator = [[[cases allKeys] sortedArrayUsingSelector:@selector(compare:)] objectEnumerator];
	NSString *caseName;
	NSMutableString *log = [NSMutableString string];
	
	while ((caseName = [enumerator nextObject])) {
		NSEnumerator *statistics = [[cases valueForKey:caseName] objectEnumerator];
		DPTestStatistics *stats;
		
		
		//printf("===== Statistics for %s: =====\n", [caseName UTF8String]);
		[log appendFormat:@"===== Statistics for %@: =====\n", caseName];
		while ((stats = [statistics nextObject]))
			[log appendFormat:@"%@\n", [stats log]];
	}
	
	return log;
}

- (NSString *)log {
	NSEnumerator *enumerator = [finishedModules objectEnumerator];
	DPTestsModule *m;
	NSMutableString *log = [NSMutableString string];
				
	while ((m = [enumerator nextObject]))
		[log appendString:[self logForModule:m]];
	
	return log;
}

- (void)logModuleStatistics:(DPTestsModule *)module {
	printf("%s", [[self logForModule:module] UTF8String]);
}

- (void)_finishModule:(DPTestsModule *)module {
	DPTestStatus s = [module status];
	
	[[NSNotificationCenter defaultCenter] removeObserver:self
													name:nil
												  object:module];
	[finishedModules addObject:module];
	[module release];
	
	if (s == DPTestSucceeded || (!s && ![self stopsOnFailedTests]))
		++completeCount;
		
	if (!s && [self stopsOnFailedTests]) {
		if (delegate && [delegate respondsToSelector:@selector(testsRunnerFinished:withStatus:)])
			[delegate testsRunnerFinished:self
							   withStatus:s];
		[self invalidate];
	} else {
		// We're ready to fire
		[self signal];
		// Wake up our runloop
		CFRunLoopWakeUp(CFRunLoopGetCurrent());
	}
}

- (void)moduleFinished:(NSNotification *)notif {
	DPTestsModule *module = [notif object];
	
	if ([self isVerbose])
		printf("Finished execution of %s module.\n", [[module name] UTF8String]);
	[self _finishModule:module];
}

- (void)moduleFailed:(NSNotification *)notif {
	DPTestsModule *module = [notif object];
	
	//[self logModuleStatistics:module];
	
	// Notify our delegate
	if (delegate && [delegate respondsToSelector:@selector(testsRunner:reportedFailedTest:)])
		[delegate testsRunner:self reportedFailedTest:[[notif userInfo] valueForKey:DPTestStatisticsKey]];
	
	[self _finishModule:module];
}

- (BOOL)stopsOnFailedTests {
	return stopOnFailure;
}

- (void)setStopsOnFailedTests:(BOOL)flag {
	stopOnFailure = flag;
}

- (BOOL)catchExceptions {
	return catchExceptions;
}

- (void)setCatchExceptions:(BOOL)flag {
	catchExceptions = flag;
}

- (void)setDelegate:(id)aDelegate {
	if (delegate != aDelegate) {
		[delegate release];
		delegate = [aDelegate retain];
	}
}

- (id)delegate {
	return delegate;
}

- (BOOL)isVerbose {
	return isVerbose;
}

- (void)setIsVerbose:(BOOL)flag {
	isVerbose = flag;
}

@end
